#include "sdUniverse.h"
#include <iostream>

using namespace std;
sdUniverse* sdUniverse::this_;
std::map<cpShape*, sdObject*> sdUniverse::objects_;
std::set<sdObject*> sdUniverse::doomed;
std::set<sdTimed*> sdUniverse::timed_;
std::set<sdBad*> sdUniverse::bad_;
bool sdUniverse::killPlayer_;
int sdUniverse::kills;

int sdUniverse::sdCollisionBW(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data) {
    sdTimed* bullet = (sdTimed*) objects_[a];
    bullet->setTtl(0.3);
    bullet->start();
    return 1;
}

int sdUniverse::sdCollisionSW(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data) {
    return 1;
}

int sdUniverse::sdCollisionBS(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data) {
    return 1;
}

int sdUniverse::sdCollisionBE(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data) {
    sdTimed* bullet = (sdTimed*) objects_[a];
    bullet->setTtl(0.3);
    bullet->start();

    sdBad* bad = (sdBad*) objects_[b];
    bad->takeDamage(1);

    if(bad->dead()) {
        doomed.insert(bad);
        bad_.erase(bad);

        std::set<sdTimed*> chunks;
        bad->getChunks(&chunks);

        for(std::set<sdTimed*>::iterator it = chunks.begin(); it != chunks.end(); ++it) {
            this_->insert(*(*it));
            timed_.insert(*it);
        }

        ++kills;
    }

    return 1;
}

int sdUniverse::sdCollisionSE(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data) {
    sdShip* ship = (sdShip*) objects_[a];
    ship->takeDamage(1);

    sdBad* bad = (sdBad*) objects_[b];

    doomed.insert(bad);
    bad_.erase(bad);

    std::set<sdTimed*> chunks;
    bad->getChunks(&chunks);

    for(std::set<sdTimed*>::iterator it = chunks.begin(); it != chunks.end(); ++it) {
        this_->insert(*(*it));
        timed_.insert(*it);
    }

    if(ship->dead()) {
        std::set<sdTimed*> shipChunks;
        ship->getChunks(&shipChunks);

        for(std::set<sdTimed*>::iterator it = shipChunks.begin(); it != shipChunks.end(); ++it) {
            this_->insert(*(*it));
            timed_.insert(*it);
        }

        killPlayer_ = true;
    }

    return 1;
}

void sdUniverse::addObstical(int numVerts, cpVect* verts, cpVect offset) {
    cpShape* shape;

    shape = cpPolyShapeNew(statics_, numVerts, verts, offset);
    shape->e = 1.0;
    shape->u = 1.0;
    cpSpaceAddStaticShape(space_, shape);
}

void sdUniverse::insert(sdObject& o) {
    cpSpaceAddBody(space_, &(o.getBody())); 
    for(std::vector<cpShape*>::iterator it = o.getShapes().begin(); it != o.getShapes().end(); ++it) {
        cpSpaceAddShape(space_, *it);
        objects_[*it] = &o;
    }
}

void sdUniverse::init() {
    statics_ = cpBodyNew(INFINITY, INFINITY);
    int width = input_.getWidth();
    int height = input_.getHeight();

    if(!isDefault_){
        cout << "non default\n";
        client_ = new Client(ip1_, ip2_, ip3_, ip4_); 
    } 
    else
        client_ = new Client();
    insert(player_.getShip());
    insert(netPlayer_.getShip()); 
    client_->init();

    cpResetShapeIdCounter();

    cpSpaceResizeStaticHash(space_, 700.0, 40);
    space_->gravity = cpv(0, 0);

    cpSpaceResizeActiveHash(space_, 5.0, 6000);
    cpSpaceAddCollisionPairFunc(space_, 2, 0, sdUniverse::sdCollisionBW, 0);
    cpSpaceAddCollisionPairFunc(space_, 1, 0, sdUniverse::sdCollisionSW, 0);
    cpSpaceAddCollisionPairFunc(space_, 2, 1, sdUniverse::sdCollisionBS, 0);
    cpSpaceAddCollisionPairFunc(space_, 2, 3, sdUniverse::sdCollisionBE, 0);
    cpSpaceAddCollisionPairFunc(space_, 1, 3, sdUniverse::sdCollisionSE, 0);

    sdObject left(statics_);
    sdObject right(statics_);
    sdObject top(statics_);
    sdObject bottom(statics_);

    int walls = 4;
    int wallWidth = 40;
    int vertWallFromSides = -wallWidth;
    int horzWallFromSides = -wallWidth;

    cpVect vLeft[] = {
        cpv(-((width / 2) - vertWallFromSides),             -((height / 2) - horzWallFromSides)),
        cpv(-((width / 2) - vertWallFromSides),              ((height / 2) - horzWallFromSides)),
        cpv(-((width / 2) - vertWallFromSides - wallWidth),  ((height / 2) - horzWallFromSides)),
        cpv(-((width / 2) - vertWallFromSides - wallWidth), -((height / 2) - horzWallFromSides)),
    };

    cpVect vRight[] = {
        cpv(((width / 2) - vertWallFromSides - wallWidth), -((height / 2) - horzWallFromSides)),
        cpv(((width / 2) - vertWallFromSides - wallWidth),  ((height / 2) - horzWallFromSides)),
        cpv(((width / 2) - vertWallFromSides),              ((height / 2) - horzWallFromSides)),
        cpv(((width / 2) - vertWallFromSides),             -((height / 2) - horzWallFromSides)),
    };

    cpVect vTop[] = {
        cpv(-((width / 2) - vertWallFromSides), ((height / 2) - horzWallFromSides - wallWidth)),
        cpv(-((width / 2) - vertWallFromSides), ((height / 2) - horzWallFromSides)),
        cpv( ((width / 2) - vertWallFromSides), ((height / 2) - horzWallFromSides)),
        cpv( ((width / 2) - vertWallFromSides), ((height / 2) - horzWallFromSides - wallWidth)),
    };

    cpVect vBottom[] = {
        cpv(-((width / 2) - vertWallFromSides), -((height / 2) - horzWallFromSides)),
        cpv(-((width / 2) - vertWallFromSides), -((height / 2) - horzWallFromSides - wallWidth)),
        cpv( ((width / 2) - vertWallFromSides), -((height / 2) - horzWallFromSides - wallWidth)),
        cpv( ((width / 2) - vertWallFromSides), -((height / 2) - horzWallFromSides)),
    };

    left.addPolyShape(walls, vLeft, cpvzero, 0);
    right.addPolyShape(walls, vRight, cpvzero, 0);
    top.addPolyShape(walls, vTop, cpvzero, 0);
    bottom.addPolyShape(walls, vBottom, cpvzero, 0);

    insert(left);
    insert(right);
    insert(top);
    insert(bottom);
}

void sdUniverse::updateObjects(cpFloat ticks) {
    std::set<sdTimed*>::iterator deleteMe;
    bool deletePrev = false;

    for(std::set<sdTimed*>::iterator ui = timed_.begin(); ui != timed_.end(); ++ui) {
        if(deletePrev) {
            timed_.erase(deleteMe);
            deletePrev = false;
        }
        (*ui)->update(ticks);
        if((*ui)->dead()) {
            doomed.insert(*ui);
            for(std::vector<cpShape*>::iterator jt = (*ui)->getShapes().begin(); jt != (*ui)->getShapes().end(); ++jt)
                objects_.erase(*jt);

            if(ui != timed_.end()) {
                (*ui)->destroy(space_);
                deleteMe = ui;
                deletePrev = true;
            }

        }
    }
    if(deletePrev) {
        timed_.erase(deleteMe);
        deletePrev = false;
    }
}

void sdUniverse::kill() {
    for(set<sdObject*>::iterator it = doomed.begin(); it != doomed.end(); ++it) {
        for(vector<cpShape*>::iterator jt = (*it)->getShapes().begin(); jt != (*it)->getShapes().end(); ++jt)
            objects_.erase(*jt);
        (*it)->destroy(space_);
        delete (*it);
    }
}

void sdUniverse::moveBads() {
    for(set<sdBad*>::iterator it = bad_.begin(); it != bad_.end(); ++it)
        (*it)->follow();
}

void sdUniverse::addBad(int hp, cpFloat velocity, cpFloat turn, cpVect pos) {
    sdBad* bad = new sdBad(hp, velocity, turn, pos);
    bad->setTarget(&(getPlayer().getShip()));
    insert(*bad);
    bad_.insert(bad);
}

void sdUniverse::update() {
    cpFloat dt = 0.5/60.0;
    cpSpaceStep(space_, dt);
    static int weapon = 1;
    player_.getShip().getPos();
    kill();
    doomed.clear();
    updateObjects(dt);
    if(getPlayer().alive()) {
        unsigned char *myposition = (unsigned char*)cpvstr(getPlayer().getShip().getPos());
            // myposition holds the string representation of the player's position
        client_->sendpacket(myposition); 
            // send position to server
        cpVect mouseLoc = getInputClass().getMouse(); 
        getPlayer().turn(mouseLoc);
        cpVect direction = getInputClass().getDirection();
        getPlayer().move(direction);
        
        if(getNetPlayer().alive()){
            unsigned char netPlayerPosition[256]; // a holder for the data from server
            client_->receivepacket(netPlayerPosition); // get the packet
            cpVect nppVec = client_->stringToVect(netPlayerPosition); // the net player's position
            getNetPlayer().getShip().setPos(nppVec); // set the position of the other player
        }
        if(getInputClass()['c']) {
            sdTimed* bullet = new sdTimed(2.0, cpBodyNew(0, 0));
            if(getPlayer().shoot(weapon, *bullet)) {
                insert(*bullet);
                timed_.insert(bullet);
            } else {
                delete bullet;
            }
        }

        if(timeToSpawn_ < 0) {
            if(nextSpawnTime_ > 0.1) {
                nextSpawnTime_ -= 0.05;
            }
            timeToSpawn_ = nextSpawnTime_;
            addBad(10, badVel += 500.0, badTurn += 0.5, randomLoc());
            } else {
                timeToSpawn_ -= dt;
            }

            moveBads();
        }

        if(killPlayer_) {
            for(vector<cpShape*>::iterator jt = getPlayer().getShip().getShapes().begin(); jt != getPlayer().getShip().getShapes().end(); ++jt) {
                objects_.erase(*jt);
            }
            getPlayer().getShip().destroy(space_);
            killPlayer_ = false;
            std::cout << "You survived for " << kills << " kills" << std::endl;
        }
    }

